/* $FreeBSD: releng/11.2/stand/kshim/bsd_kernel.c 315221 2017-03-14 02:06:03Z pfg $ */
/*-
 * Copyright (c) 2013 Hans Petter Selasky. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#endif

#ifdef LOSCFG_DRIVERS_USB_HOST_DRIVER
extern volatile uint8_t g_device_is_ready;
#endif

#undef USB_DEBUG_VAR
#define USB_DEBUG_VAR usb_debug

/*------------------------------------------------------------------------*
 * Implementation of mutex API
 *------------------------------------------------------------------------*/

struct mtx Giant = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
struct mtx Gcall = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

int usb_mutex_init(struct mtx *mutex, unsigned int flag)
{
    pthread_mutexattr_t attr;

    (void)pthread_mutexattr_init(&attr);

    if (flag & MTX_RECURSE) {
        (void)pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    }

    return pthread_mutex_init(mutex, &attr);
}

/*------------------------------------------------------------------------*
 * Implementation of device API
 *------------------------------------------------------------------------*/

static const char unknown_string[] = { "unknown" };


static TAILQ_HEAD(, module_data) module_head =
    TAILQ_HEAD_INITIALIZER(module_head);

static uint8_t
devclass_equal(const char *a, const char *b)
{
    char ta, tb;

    if (a == b)
        return (1);
    if((!a)||(!b))
        return (0);
    while (1) {
        ta = *a;
        tb = *b;
        if (ta != tb)
            return (0);
        if (ta == 0)
            break;
        a++;
        b++;
    }
    return (1);
}

int
bus_generic_resume(device_t dev)
{
    return (0);
}

int
bus_generic_shutdown(device_t dev)
{
    return (0);
}

int
bus_generic_suspend(device_t dev)
{
    return (0);
}

int
bus_generic_print_child(device_t dev, device_t child)
{
    return (0);
}

void
bus_generic_driver_added(device_t dev, driver_t *driver)
{
    return;
}

device_t
device_get_parent(device_t dev)
{
    return (dev ? dev->dev_parent : NULL);
}

void
device_set_interrupt(device_t dev, driver_filter_t *filter,
    driver_intr_t *fn, void *arg)
{
    dev->dev_irq_filter = filter;
    dev->dev_irq_fn = fn;
    dev->dev_irq_arg = arg;
}

void
device_run_interrupts(device_t parent)
{
    device_t child = NULL;

    if (parent == NULL) {
        return;
    }

    TAILQ_FOREACH(child, &parent->dev_children, dev_link) {
        int status;
        if (child->dev_irq_filter != NULL)
            status = child->dev_irq_filter(child->dev_irq_arg);
        else
            status = FILTER_SCHEDULE_THREAD;

        if (status == FILTER_SCHEDULE_THREAD) {
            if (child->dev_irq_fn != NULL)
                (child->dev_irq_fn) (child->dev_irq_arg);
        }
    }
}

void
device_set_ivars(device_t dev, void *ivars)
{
    dev->dev_aux = ivars;
}

void   *
device_get_ivars(device_t dev)
{
    return (dev ? dev->dev_aux : NULL);
}

unsigned int
device_get_unit(device_t dev)
{
    return (dev ? dev->dev_unit : 0U);
}

int
bus_generic_detach(device_t dev)
{
    device_t child = NULL;
    int error;

    if (!dev->dev_attached) {
        return (EBUSY);
    }

    TAILQ_FOREACH(child, &dev->dev_children, dev_link) {
        if ((error = device_detach(child)) != 0) {
            return (error);
        }
    }
    return (0);
}

const char *
device_get_nameunit(device_t dev)
{
    if (dev && dev->dev_nameunit[0])
        return (dev->dev_nameunit);

    return (unknown_string);
}

static uint8_t
devclass_create(devclass_t *dc_pp)
{
    if (dc_pp == NULL) {
        return (1);
    }
    if (dc_pp[0] == NULL) {
        dc_pp[0] = usbmalloc(sizeof(**(dc_pp)),
            M_DEVBUF, M_WAITOK | M_ZERO);

        if (dc_pp[0] == NULL) {
            return (1);
        }
    }
    return (0);
}

void devclass_module_dump(void) /*lint -e529*/
{
    int index = 0;
    const struct module_data *mod = NULL;

    TAILQ_FOREACH(mod, &module_head, entry) {
        DPRINTF("%d  modulePath: %s\n",index++, mod->long_name);
    }
    (void)index; // this is for clearing warning
} /*lint +e529*/

static const struct module_data *
devclass_find_create(const char *classname)
{
    const struct module_data *mod = NULL;

    TAILQ_FOREACH(mod, &module_head, entry) {
        if (devclass_equal(mod->mod_name, classname)) {
            if (devclass_create(mod->devclass_pp)) {
                continue;
            }
            return (mod);
        }
    }
    return (NULL);
}

struct module_data *
bus_get_device(const char *modname)
{
    static struct module_data *mod;

    TAILQ_FOREACH(mod, &module_head, entry) {
        if (devclass_equal(mod->mod_name, modname)) {
            return (mod);
        }
    }
    return (NULL);
}

static uint8_t
devclass_add_device(const struct module_data *mod, device_t dev)
{
    device_t *pp_dev = NULL;
    device_t *end = NULL;
    uint8_t unit;
    int ret;

    pp_dev = mod->devclass_pp[0]->dev_list; /*lint !e613*/
    end = pp_dev + DEVCLASS_MAXUNIT;
    unit = 0;

    while (pp_dev != end) {
        if (*pp_dev == NULL) {
            *pp_dev = dev;
            dev->dev_unit = unit;
            dev->dev_module = mod;
            ret = snprintf_s(dev->dev_nameunit, sizeof(dev->dev_nameunit),
                sizeof(dev->dev_nameunit) - 1, "%s%d", device_get_name(dev), unit);
            if (ret < 0) {
                return (1);
            }
            return (0);
        }
        pp_dev++;
        unit++;
    }
    DPRINTF("Could not add device to devclass.\n");
    return (1);
}

static void
devclass_delete_device(device_t dev)
{
    const struct module_data *mod = dev->dev_module;
    if (mod == NULL) {
        return;
    }
    mod->devclass_pp[0]->dev_list[dev->dev_unit] = NULL;
    dev->dev_module = NULL;
}

static device_t
make_device(device_t parent, const char *name)
{
    device_t dev = NULL;
    const struct module_data *mod = NULL;

    if (name != NULL) {
        mod = devclass_find_create(name);
        if (mod == NULL) {
            DPRINTF("%s:%d:%s: can't find device "
                "class %s\n", __FILE__, __LINE__,
                __FUNCTION__, name);

            goto done;
        }
    }

    dev = usbmalloc(sizeof(*dev), M_DEVBUF, M_WAITOK | M_ZERO);
    if (dev == NULL) {
        goto done;
    }

    dev->dev_parent = parent;
    TAILQ_INIT(&dev->dev_children);

    if (name != NULL) {
        dev->dev_fixed_class = 1;
        if (devclass_add_device(mod, dev)) {
            goto error;
        }
    }
done:
    return (dev);

error:
    if (dev != NULL) {
        usbfree(dev, M_DEVBUF);
    }
    return (NULL);
}

device_t
device_add_child(device_t dev, const char *name, int unit)
{
    device_t child;

    if (unit != -1) {
        device_printf(dev, "Unit is not -1\n");
    }
    child = make_device(dev, name);
    if (child == NULL) {
        device_printf(dev, "Could not add child '%s'\n", name);
        goto done;
    }
    if (dev == NULL) {
        /* no parent */
        goto done;
    }
    TAILQ_INSERT_TAIL(&dev->dev_children, child, dev_link);
done:
    return (child);
}

int
device_delete_child(device_t dev, device_t child)
{
    int error = 0;
    device_t grandchild;

    /* detach parent before deleting children, if any */
    error = device_detach(child);
    if (error)
        goto done;

    /* remove children second */
    while ((grandchild = TAILQ_FIRST(&child->dev_children))) {
        error = device_delete_child(child, grandchild);
        if (error) {
            device_printf(dev, "Error deleting child!\n");
            goto done;
        }
    }

    devclass_delete_device(child);

    if (dev != NULL) {
        /* remove child from parent */
        TAILQ_REMOVE(&dev->dev_children, child, dev_link);
    }
    usbfree(child, M_DEVBUF);

done:
    return (error);
}

int
device_delete_children(device_t dev)
{
    device_t child;
    int error = 0;

    while ((child = TAILQ_FIRST(&dev->dev_children))) {
        error = device_delete_child(dev, child);
        if (error) {
            device_printf(dev, "Error deleting child!\n");
            break;
        }
    }
    return (error);
}

void
device_quiet(device_t dev)
{
    dev->dev_quiet = 1;
}

const char *
device_get_desc(device_t dev)
{
    if (dev)
        return &(dev->dev_desc[0]);
    return (unknown_string);
}

static int
default_method(void)
{
    /* do nothing */
    DPRINTF("Default method called\n");
    return (0);
}

void   *
device_get_method(device_t dev, const char *what)
{
    const struct device_method *mtod;

    mtod = dev->dev_module->driver->methods;
    while (mtod->func != NULL) {
        if (devclass_equal(mtod->desc, what)) {
            return (mtod->func);
        }
        mtod++;
    }
    return ((void *)&default_method); /*lint !e611 !e546*/
}

const char *
device_get_name(device_t dev)
{
    if (dev == NULL)
        return (unknown_string);

    return (dev->dev_module->driver->name);
}

static int
device_allocate_softc(device_t dev)
{
    const struct module_data *mod;

    mod = dev->dev_module;

    if ((dev->dev_softc_alloc == 0) &&
        (mod->driver->size != 0)) {
        dev->dev_sc = usbmalloc(mod->driver->size,
            M_DEVBUF, M_WAITOK | M_ZERO);

        if (dev->dev_sc == NULL)
            return (ENOMEM);

        dev->dev_softc_alloc = 1;
    }
    return (0);
}

int
device_probe_and_attach(device_t dev) /*lint -e438*/
{
    const struct module_data *mod = NULL;
    const char *bus_name_parent = NULL;

    if (dev->dev_attached) {
        return (0);     /* fail-safe */
    }

    if (dev->dev_fixed_class) {
        mod = dev->dev_module;
        DPRINTF("module bus_name(%s), mod_name(%s)\n", mod->bus_name, mod->mod_name);
        if (DEVICE_PROBE(dev) <= 0) { /*lint !e611*/
            if (device_allocate_softc(dev) == 0) {
                if (DEVICE_ATTACH(dev) == 0) { /*lint !e611*/
                    /* success */
                    dev->dev_attached = 1;
                    return (0);
                }
            }
        }
        (void)device_detach(dev);
        goto error;
    }

    /* Else find a module for our device, if any */
    bus_name_parent = device_get_name(device_get_parent(dev));
    DPRINTF("%d:%s\n", __LINE__, bus_name_parent);
    TAILQ_FOREACH(mod, &module_head, entry) {
        if (devclass_equal(mod->bus_name, bus_name_parent)) {/*lint !e644*/
            if (devclass_create(mod->devclass_pp)) {
                continue;
            }
            if (devclass_add_device(mod, dev)) {
                continue;
            }
            if (DEVICE_PROBE(dev) <= 0) { /*lint !e611*/
                if (device_allocate_softc(dev) == 0) {
                    if (DEVICE_ATTACH(dev) == 0) { /*lint !e611*/
                        /* success */
                        dev->dev_attached = 1;

#ifdef LOSCFG_DRIVERS_USB_HOST_DRIVER
                        if (!devclass_equal(mod->mod_name, "uhub")) {
                            g_device_is_ready = 1;
                        }
#endif
                        return (0);
                    }
                }
            }
            /* else try next driver */

            (void)device_detach(dev);
        }
    }

error:
    return (ENODEV);
}/*lint +e438*/

int
device_detach(device_t dev)
{
    int error;

    if (dev->dev_attached) {
#ifdef LOSCFG_DRIVERS_USB_HOST_DRIVER
        g_device_is_ready = 0;
#endif
        error = DEVICE_DETACH(dev); /*lint !e611*/
        if (error) {
            return error;
        }
        dev->dev_attached = 0;
    }
    device_set_softc(dev, NULL);

    if (dev->dev_fixed_class == 0)
        devclass_delete_device(dev);

    return (0);
}

void
device_set_softc(device_t dev, void *softc)
{
    if (dev->dev_softc_alloc) {
        usbfree(dev->dev_sc, M_DEVBUF);
        dev->dev_sc = NULL;
    }
    dev->dev_sc = softc;
    dev->dev_softc_alloc = 0;
}

void   *
device_get_softc(device_t dev)
{
    if (dev == NULL)
        return (NULL);

    return (dev->dev_sc);
}

int
device_is_attached(device_t dev)
{
    return (dev->dev_attached);
}

void
device_set_desc(device_t dev, const char *desc)
{
    int ret;

    ret = snprintf_s(dev->dev_desc, sizeof(dev->dev_desc), sizeof(dev->dev_desc) - 1, "%s", desc);
    if (ret < 0) {
        usb_err("snprintf_s fail, %s \n", desc);
    }
    return;
}

void
device_set_desc_copy(device_t dev, const char *desc)
{
    device_set_desc(dev, desc);
}

void   *
devclass_get_softc(devclass_t dc, int unit)
{
    return (device_get_softc(devclass_get_device(dc, unit)));
}

int
devclass_get_maxunit(devclass_t dc)
{
    int max_unit = 0;

    if (dc) {
        max_unit = DEVCLASS_MAXUNIT;
        while (max_unit--) {
            if (dc->dev_list[max_unit]) {
                break;
            }
        }
        max_unit++;
    }
    return (max_unit);
}

device_t
devclass_get_device(devclass_t dc, int unit)
{
    return (((unit < 0) || (unit >= DEVCLASS_MAXUNIT) || (dc == NULL)) ?
        NULL : dc->dev_list[unit]);
}

devclass_t
devclass_find(const char *classname)
{
    const struct module_data *mod = NULL;

    TAILQ_FOREACH(mod, &module_head, entry) {
        if (devclass_equal(mod->mod_name, classname)) {
            return (mod->devclass_pp[0]);
        }
    }
    return (NULL);
}

void
module_register(void *data)
{
    struct module_data *mdata = data;

    TAILQ_INSERT_TAIL(&module_head, mdata, entry);
}

void
module_unregister(void *data)
{
    struct module_data *mdata = data;
    TAILQ_REMOVE(&module_head, mdata, entry);
}

#ifdef LOSCFG_USB_DEBUG
static TAILQ_HEAD(, debug_module_data) debug_module_head =
    TAILQ_HEAD_INITIALIZER(debug_module_head);

void
debug_module_register(void *data)
{
    struct debug_module_data *mdata = data;
    TAILQ_INSERT_TAIL(&debug_module_head, mdata, entry);
}

void
debug_module_unregister(void *data)
{
    struct debug_module_data *mdata = data;
    TAILQ_REMOVE(&debug_module_head, mdata, entry);
}

struct debug_module_data *
get_debug_module(const char *modname)
{
    static struct debug_module_data *mod;

    TAILQ_FOREACH(mod, &debug_module_head, entry) {
        if (devclass_equal(mod->mod_name, modname)) {
            return (mod);
        }
    }
    return (NULL);
}

void debug_module_dump(void) /*lint -e529*/
{
    int index = 0;
    const struct debug_module_data *mod = NULL;

    TAILQ_FOREACH(mod, &debug_module_head, entry) {
        PRINTK("%s\n", mod->mod_name);
    }
    (void)index; //this is for clearing warning
} /*lint +e529*/
#endif
#undef USB_DEBUG_VAR
